/* ========================================================================
 * Copyright (c) 2005-2018 The OPC Foundation, Inc. All rights reserved.
 *
 * OPC Foundation MIT License 1.00
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * The complete license agreement can be found here:
 * http://opcfoundation.org/License/MIT/1.00/
 * ======================================================================*/

/******************************************************************************************************/
/* Platform Portability Layer                                                                         */
/* Modify the content of this file according to the mutex implementation on your system.              */
/* Hint: Emulate with binary semaphore                                                                */
/******************************************************************************************************/

/* System Headers */

#define OPCUA_MUTEX_USE_SPINCOUNT 0

#if OPCUA_MUTEX_USE_SPINCOUNT
#define _WIN32_WINNT  0x0403
#endif

#include <windows.h>
#include <stdio.h>

/* UA platform definitions */
#include <opcua_p_internal.h>
#include <opcua_p_memory.h>

/* own headers */
#include <opcua_mutex.h>
#include <opcua_p_mutex.h>

#if OPCUA_MUTEX_ERROR_CHECKING
#include <opcua_p_thread.h>
#endif


#if OPCUA_MUTEX_ERROR_CHECKING

/* not maintainded code */

#include <opcua_thread.h> /* for thread id */

OpcUa_Int32 g_nMutexId = 0;

struct _OpcUa_P_InternalMutex
{
    OpcUa_Void*     pSystemMutex;
    OpcUa_Int32     nMutexId;
    OpcUa_UInt32    uThreadId;
    OpcUa_Int32     nLockCount;
};
typedef struct _OpcUa_P_InternalMutex OpcUa_P_InternalMutex;

/*============================================================================
 * Allocate the mutex.
 *===========================================================================*/
OpcUa_StatusCode OPCUA_DLLCALL OpcUa_P_Mutex_CreateImp(OpcUa_Mutex* phMutex, char* file, int line)
{
    OpcUa_StatusCode uStatus = OpcUa_Good;
    CRITICAL_SECTION *pCS;
    OpcUa_P_InternalMutex* pInternalMutex = OpcUa_Null;

    if(phMutex == OpcUa_Null)
    {
        printf("Invalid Argument Error in: OpcUa_P_Mutex_Create!\n");
        return OpcUa_BadInvalidArgument;
    }

    pCS = OpcUa_P_Memory_Alloc(sizeof(CRITICAL_SECTION));
    if(pCS == NULL)
    {
        printf("Out of memory Error in: OpcUa_P_Mutex_Create!\n");
        return OpcUa_BadOutOfMemory;
    }

    pInternalMutex = (OpcUa_P_InternalMutex*)OpcUa_P_Memory_Alloc(sizeof(OpcUa_P_InternalMutex));
    if(pInternalMutex == NULL)
    {
        printf("Out of memory Error in: OpcUa_P_Mutex_Create!\n");
        return OpcUa_BadOutOfMemory;
    }

    g_nMutexId++;

    pInternalMutex->pSystemMutex = pCS;
    pInternalMutex->nLockCount = 0;
    pInternalMutex->uThreadId  = 0;
    pInternalMutex->nMutexId   = g_nMutexId;

    InitializeCriticalSection(pCS);

    *phMutex = (OpcUa_Mutex)pInternalMutex;

    /* printf("Created  Mutex%d, File: %s, Line: %d\n", pInternalMutex->nMutexId, file, line); */

    return uStatus;
}

/*============================================================================
 * Clear and free the mutex.
 *===========================================================================*/
OpcUa_Void OPCUA_DLLCALL OpcUa_P_Mutex_DeleteImp(OpcUa_Mutex* phMutex, char* file, int line)
{
    OpcUa_Int32 nMutexId;
    CRITICAL_SECTION *pCS = NULL;
    OpcUa_P_InternalMutex* pInternalMutex = OpcUa_Null;

    if(phMutex == OpcUa_Null || *phMutex == OpcUa_Null)
    {
        printf("Invalid Argument Error in: OpcUa_P_Mutex_Delete!\n");
        return;
    }

    pInternalMutex = (OpcUa_P_InternalMutex*)*phMutex;

    pCS = (CRITICAL_SECTION*)pInternalMutex->pSystemMutex;

    if(pInternalMutex->nLockCount != 0)
    {
        printf("Error in OpcUa_P_Mutex_Delete. LockCount != 0.\n");
    }

    nMutexId = pInternalMutex->nMutexId;
    OpcUa_P_Memory_Free(pInternalMutex);

    DeleteCriticalSection(pCS);

    OpcUa_P_Memory_Free(pCS);

    *phMutex = OpcUa_Null;

    /* printf("Deleted Mutex%d, File: %s, Line: %d\n", nMutexId, file, line); */

    return;
}

/*============================================================================
 * Lock the mutex.
 *===========================================================================*/
OpcUa_Void OPCUA_DLLCALL OpcUa_P_Mutex_LockImp(OpcUa_Mutex hMutex, char* file, int line)
{
    OpcUa_P_InternalMutex* pInternalMutex = OpcUa_Null;

    if(hMutex == OpcUa_Null)
    {
        /* Debug */
        printf("InvalidArgument Error in OpcUa_P_Mutex_Lock: File: %s, Line: %d\n", file, line);
        return;
    }

    pInternalMutex = (OpcUa_P_InternalMutex*)hMutex;

    EnterCriticalSection((CRITICAL_SECTION*)pInternalMutex->pSystemMutex);

    pInternalMutex->nLockCount++;

    pInternalMutex->uThreadId = OpcUa_P_Thread_GetCurrentThreadId();

    /* printf("Locked   Mutex%d ThreadID: %d, LockCount: %d, File: %s, Line: %d\n", pInternalMutex->nMutexId, pInternalMutex->uThreadId, pInternalMutex->nLockCount, file, line); */

    hMutex = (OpcUa_Mutex)pInternalMutex;

    return;
}

/*============================================================================
 * Unlock the mutex.
 *===========================================================================*/
OpcUa_Void OPCUA_DLLCALL OpcUa_P_Mutex_UnlockImp(OpcUa_Mutex hMutex, char* file, int line)
{
    OpcUa_P_InternalMutex* pInternalMutex = OpcUa_Null;

    if(hMutex == OpcUa_Null)
    {
        /* debug */
        printf("InvalidArgument Error in OpcUa_P_Mutex_Unlock: File: %s, Line: %d\n", file, line);
        return;
    }

    pInternalMutex = (OpcUa_P_InternalMutex*)hMutex;

    if(pInternalMutex->nLockCount == 0)
    {
        printf("(ERROR) Unlocking Mutex%d ThreadID: %d, Count: %d, File: %s, Line: %d\n", pInternalMutex->nMutexId, pInternalMutex->uThreadId, pInternalMutex->nLockCount, file, line);
        return;
    }

    if(pInternalMutex->uThreadId != OpcUa_P_Thread_GetCurrentThreadId())
    {
        printf("(ERROR) Unlocking Mutex%d ThreadID: %d, Count: %d, File: %s, Line: %d\n", pInternalMutex->nMutexId, pInternalMutex->uThreadId, pInternalMutex->nLockCount, file, line);
        return;
    }

    pInternalMutex->nLockCount--;

    /* printf("Unlocked Mutex%d ThreadID: %d, New LockCount: %d, File: %s, Line: %d\n", pInternalMutex->nMutexId, pInternalMutex->uThreadId, pInternalMutex->nLockCount, file, line); */

    hMutex = (OpcUa_Mutex)pInternalMutex;

    LeaveCriticalSection((CRITICAL_SECTION*)pInternalMutex->pSystemMutex);

    return;
}
#else /* OPCUA_MUTEX_ERROR_CHECKING */
/*============================================================================
 * Allocate the mutex.
 *===========================================================================*/
OpcUa_StatusCode OPCUA_DLLCALL OpcUa_P_Mutex_CreateImp(OpcUa_Mutex* a_phMutex)
{
    OpcUa_StatusCode uStatus = OpcUa_Good;
    LPCRITICAL_SECTION lpCriticalSection = NULL;

#if OPCUA_MUTEX_USE_SPINCOUNT
    BOOL bRet;
    LONG hr;
#endif

    if(a_phMutex == OpcUa_Null)
    {
        return OpcUa_BadInvalidArgument;
    }

    if(*a_phMutex != OpcUa_Null)
    {
        *a_phMutex = OpcUa_Null;
    }

    lpCriticalSection = OpcUa_P_Memory_Alloc(sizeof(CRITICAL_SECTION));

    if(lpCriticalSection == NULL)
    {
        return OpcUa_BadOutOfMemory;
    }

#if OPCUA_MUTEX_USE_SPINCOUNT
    bRet = InitializeCriticalSectionAndSpinCount(   lpCriticalSection,
                                                    0x00000400) ;
    if (bRet == 0)
    {
        hr = GetLastError();
        OpcUa_P_Memory_Free(lpCriticalSection);
        return OpcUa_Bad;
    }

#else
    InitializeCriticalSection(lpCriticalSection);
#endif

    *a_phMutex = (OpcUa_Mutex)lpCriticalSection;

    return uStatus;
}

/*============================================================================
 * Clear and free the mutex.
 *===========================================================================*/
OpcUa_Void OPCUA_DLLCALL OpcUa_P_Mutex_DeleteImp(OpcUa_Mutex* a_phMutex)
{
    LPCRITICAL_SECTION lpCriticalSection = NULL;
    if(a_phMutex == OpcUa_Null || *a_phMutex == OpcUa_Null)
    {
        return;
    }

    lpCriticalSection = (LPCRITICAL_SECTION)*a_phMutex;

    DeleteCriticalSection(lpCriticalSection);

    OpcUa_P_Memory_Free(lpCriticalSection);

    *a_phMutex = OpcUa_Null;

    return;
}

/*============================================================================
 * Lock the mutex.
 *===========================================================================*/
OpcUa_Void OPCUA_DLLCALL OpcUa_P_Mutex_LockImp(OpcUa_Mutex hMutex)
{
    if(hMutex == OpcUa_Null)
    {
        /* Debug */
        /* printf("Error: OpcUa_P_Mutex_Lock!\n"); */
        return;
    }

    EnterCriticalSection((CRITICAL_SECTION*)hMutex);

    return;
}

/*============================================================================
 * Unlock the mutex.
 *===========================================================================*/
OpcUa_Void OPCUA_DLLCALL OpcUa_P_Mutex_UnlockImp(OpcUa_Mutex hMutex)
{
    if(hMutex == OpcUa_Null)
    {
        /* debug */
        /* printf("Error: OpcUa_P_Mutex_Unlock!\n"); */
        return;
    }

    LeaveCriticalSection((CRITICAL_SECTION*)hMutex);

    return;
}
#endif /* OPCUA_MUTEX_ERROR_CHECKING */
